// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:io';

import 'package:path/path.dart' as p;
import 'package:pub/src/exit_codes.dart';
import 'package:test/test.dart';

import '../descriptor.dart';
import '../test_pub.dart';

Future<void> main() async {
  test(
    'Recreates .dart_tool/package_config.json, redownloads archives',
    () async {
      final server = await servePackages();
      server.serve('foo', '1.0.0');
      await appDir(dependencies: {'foo': 'any'}).create();
      await pubGet();
      final packageConfig = File(
        path(p.join(appPath, '.dart_tool', 'package_config.json')),
      );
      packageConfig.deleteSync();
      await runPub(args: ['cache', 'clean', '-f']);
      await pubGet(args: ['--enforce-lockfile']);
      expect(packageConfig.existsSync(), isTrue);
      await cacheDir({'foo': '1.0.0'}).validate();
      await appPackageConfigFile([
        packageConfigEntry(name: 'foo', version: '1.0.0'),
      ]).validate();
    },
  );

  test('Refuses to get if no lockfile exists', () async {
    await appDir(dependencies: {}).create();
    await pubGet(
      args: ['--enforce-lockfile'],
      error: '''
Retrieving dependencies failed.
Cannot do `--enforce-lockfile` without an existing `pubspec.lock`.

Try running `dart pub get` to create `pubspec.lock`.
''',
    );
  });

  test('Refuses to get in ./example if hash is updated', () async {
    final server = await servePackages();
    server.serveContentHashes = true;
    server.serve('foo', '1.0.0');
    server.serve('bar', '1.0.0');

    await appDir(dependencies: {'foo': '^1.0.0'}).create();
    await dir(appPath, [
      dir('example', [
        libPubspec(
          'example',
          '0.0.0',
          deps: {
            'bar': '1.0.0',
            'myapp': {'path': '../'},
          },
        ),
      ]),
    ]).create();
    await pubGet(args: ['--example']);

    server.serve(
      'bar',
      '1.0.0',
      contents: [
        file('README.md', 'Including this will change the content-hash.'),
      ],
    );
    // Deleting the version-listing cache will cause it to be refetched, and the
    // error will happen.
    File(
      p.join(globalServer.cachingPath, '.cache', 'bar-versions.json'),
    ).deleteSync();

    final example = p.join('.', 'example');
    final examplePubspec = p.join('example', 'pubspec.yaml');
    final examplePubspecLock = p.join('example', 'pubspec.lock');

    await pubGet(
      args: ['--enforce-lockfile', '--example'],
      output: allOf(
        contains('Got dependencies!'),
        contains('Resolving dependencies in `$example`...'),
      ),
      error: allOf(
        contains(
          'Unable to satisfy `$examplePubspec` '
          'using `$examplePubspecLock` in `$example`.',
        ),
        contains(
          'To update `$examplePubspecLock` run '
          '`dart pub get` in `$example` without\n'
          '`--enforce-lockfile`.',
        ),
      ),
      exitCode: DATA,
    );
  });

  test('Refuses to get if lockfile is missing package', () async {
    final server = await servePackages();
    server.serve('foo', '1.0.0');
    await appDir(dependencies: {}).create();
    await pubGet();
    await appDir(dependencies: {'foo': 'any'}).create();

    await pubGet(
      args: ['--enforce-lockfile'],
      output: allOf(
        contains('+ foo 1.0.0'),
        contains('Would have changed 1 dependency.'),
      ),
      error: contains('Unable to satisfy `pubspec.yaml` using `pubspec.lock`.'),
      exitCode: DATA,
    );
  });

  test(
    'Refuses to get if package is locked to version not matching constraint',
    () async {
      final server = await servePackages();
      server.serve('foo', '1.0.0');
      server.serve('foo', '2.0.0');
      await appDir(dependencies: {'foo': '^1.0.0'}).create();
      await pubGet();
      await appDir(dependencies: {'foo': '^2.0.0'}).create();
      await pubGet(
        args: ['--enforce-lockfile'],
        output: allOf([
          contains('> foo 2.0.0 (was 1.0.0)'),
          contains('Would have changed 1 dependency.'),
        ]),
        error: contains(
          'Unable to satisfy `pubspec.yaml` using `pubspec.lock`.',
        ),
        exitCode: DATA,
      );
    },
  );

  test(
    "Refuses to get if hash on server doesn't correspond to lockfile",
    () async {
      final server = await servePackages();
      server.serveContentHashes = true;
      server.serve('foo', '1.0.0');
      await appDir(dependencies: {'foo': '^1.0.0'}).create();
      await pubGet();
      server.serve(
        'foo',
        '1.0.0',
        contents: [
          file('README.md', 'Including this will change the content-hash.'),
        ],
      );
      // Deleting the version-listing cache will cause it to be refetched, and
      // the error will happen.
      File(
        p.join(globalServer.cachingPath, '.cache', 'foo-versions.json'),
      ).deleteSync();
      await pubGet(
        args: ['--enforce-lockfile'],
        output: allOf(
          contains('~ foo 1.0.0 (was 1.0.0)'),
          contains('Would have changed 1 dependency.'),
        ),
        error: allOf(
          contains(
            'Cached version of foo-1.0.0 has wrong hash - redownloading.',
          ),
          contains(
            'The existing content-hash from pubspec.lock '
            'doesn\'t match contents for:',
          ),
          contains(' * foo-1.0.0 from "${server.url}"'),
          contains('Unable to satisfy `pubspec.yaml` using `pubspec.lock`.'),
        ),
        exitCode: DATA,
      );
    },
  );

  test('Refuses to get if archive on legacy server '
      'doesn\'t have hash corresponding to lockfile', () async {
    final server = await servePackages();
    server.serveContentHashes = false;
    server.serve('foo', '1.0.0');
    await appDir(dependencies: {'foo': '^1.0.0'}).create();
    await pubGet();
    await runPub(args: ['cache', 'clean', '-f']);
    server.serve(
      'foo',
      '1.0.0',
      contents: [
        file('README.md', 'Including this will change the content-hash.'),
      ],
    );

    await pubGet(
      args: ['--enforce-lockfile'],
      output: allOf(
        contains('~ foo 1.0.0 (was 1.0.0)'),
        contains('Would have changed 1 dependency.'),
      ),
      error: allOf(
        contains('''
The existing content-hash from pubspec.lock doesn't match contents for:
 * foo-1.0.0 from "${server.url}"'''),
        contains('Unable to satisfy `pubspec.yaml` using `pubspec.lock`.'),
      ),
      exitCode: DATA,
    );
  });
}
